package com.vteba.utils.serialize;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.MessageCollectionSchema;
import com.dyuproject.protostuff.ProtobufIOUtil;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.StringMapSchema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.vteba.utils.charstr.ByteUtils;
import com.vteba.utils.charstr.Char;
import com.vteba.utils.common.ClassUtils;
import com.vteba.utils.reflection.AsmUtils;

/**
 * 基于Protostuff的序列化和反序列化工具。简化版，主要的改进在于，反序列化时，不需要传递对象了。
 * 性能稍差，主要的损失在于反射构造对象。
 * @author yinlei 
 * @since 2013-12-12 17:32
 */
public class ProtoUtils {
	
    /**
     * 将对象序列化成字节数组
     * @param object 要被序列化的对象
     * @return 序列化后的字节数组
     */
	@SuppressWarnings("unchecked")
	public static byte[] toBytes(Object object) {
        if (object == null) {
            return null;
        }
        byte[] bytes = null;
        Class<Object> clazz = null;
        if (object instanceof List) {
			List<Object> list = (List<Object>) object;
			if (list.size() == 0) {
				return null;
			}
        	clazz = (Class<Object>) list.get(0).getClass();
        	Schema<Object> schema = RuntimeSchema.getSchema(clazz);
        	MessageCollectionSchema<Object> collectionSchema = new MessageCollectionSchema<Object>(schema);
        	LinkedBuffer buffer = LinkedBuffer.allocate(2048);
        	bytes = ProtobufIOUtil.toByteArray(list, collectionSchema, buffer);
        	
            int byteLength = bytes.length;
        	String className = clazz.getName();
            byte[] nameBytes = className.getBytes(Char.UTF8);
            int length = nameBytes.length;
            byte[] destBytes = new byte[byteLength + length + 5];
            destBytes[0] = 1;
            
            ByteUtils.mergeBytes(length, destBytes, 1, 4);
            System.arraycopy(nameBytes, 0, destBytes, 5, length);
            System.arraycopy(bytes, 0, destBytes, length + 5, byteLength);
            return destBytes;
        } else if (object instanceof Set) {
        	Set<Object> set = (Set<Object>) object;
        	if (set.size() == 0) {
        		return null;
        	}
        	clazz = (Class<Object>) set.iterator().next().getClass();
        	Schema<Object> schema = RuntimeSchema.getSchema(clazz);
        	MessageCollectionSchema<Object> collectionSchema = new MessageCollectionSchema<Object>(schema);
        	LinkedBuffer buffer = LinkedBuffer.allocate(2048);
        	bytes = ProtobufIOUtil.toByteArray(set, collectionSchema, buffer);
        	
        	int byteLength = bytes.length;
        	String className = clazz.getName();
            byte[] nameBytes = className.getBytes(Char.UTF8);
            int length = nameBytes.length;
            byte[] destBytes = new byte[byteLength + length + 5];
            destBytes[0] = 2;
            
            ByteUtils.mergeBytes(length, destBytes, 1, 4);
            System.arraycopy(nameBytes, 0, destBytes, 5, length);
            System.arraycopy(bytes, 0, destBytes, length + 5, byteLength);
            return destBytes;
        } else if (object instanceof Map) {
        	Map<String, Object> map = (Map<String, Object>) object;
        	if (map.size() == 0) {
        		return null;
        	}
        	clazz = (Class<Object>) map.values().iterator().next().getClass();
        	Schema<Object> schema = RuntimeSchema.getSchema(clazz);
        	StringMapSchema<Object> collectionSchema = new StringMapSchema<Object>(schema);
        	LinkedBuffer buffer = LinkedBuffer.allocate(2048);
        	bytes = ProtobufIOUtil.toByteArray(map, collectionSchema, buffer);
        	
        	int byteLength = bytes.length;
        	String className = clazz.getName();
            byte[] nameBytes = className.getBytes(Char.UTF8);
            int length = nameBytes.length;
            byte[] destBytes = new byte[byteLength + length + 5];
            destBytes[0] = 3;
            
            ByteUtils.mergeBytes(length, destBytes, 1, 4);
            System.arraycopy(nameBytes, 0, destBytes, 5, length);
            System.arraycopy(bytes, 0, destBytes, length + 5, byteLength);
            return destBytes;
        } else {
        	clazz = (Class<Object>) object.getClass();
        	Schema<Object> schema = RuntimeSchema.getSchema(clazz);
        	LinkedBuffer buffer = LinkedBuffer.allocate(2048);
        	bytes = ProtostuffIOUtil.toByteArray(object, schema, buffer);
        	// bytes = Protos.toByteArray(object);
        	
            int byteLength = bytes.length;
        	String className = clazz.getName();
            byte[] nameBytes = className.getBytes(Char.UTF8);
            int length = nameBytes.length;
            byte[] destBytes = new byte[byteLength + length + 5];
            
            ByteUtils.mergeBytes(length, destBytes, 1, 4);
            System.arraycopy(nameBytes, 0, destBytes, 5, length);
            System.arraycopy(bytes, 0, destBytes, length + 5, byteLength);
            
            return destBytes;
        }
    }
    
    /**
     * 将字节数组反序列化成对象
     * @param bytes 字节数组
     * @return 反序列化后的对象
     */
    @SuppressWarnings("unchecked")
	public static <T> T fromBytes(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        
        int byteLength = bytes.length;
        int type = bytes[0];
        
        int length = getLength(bytes);
        byte[] nameBytes = new byte[length];
        System.arraycopy(bytes, 5, nameBytes, 0, length);
        
        String className = new String(nameBytes, Char.UTF8);
        Class<Object> clazz = ClassUtils.forName(className);
        Schema<Object> schema = RuntimeSchema.getSchema(clazz);
        
        int offset = length + 5;
        int destLength = byteLength - offset;
        
        if (type == 0) {
        	Object entity = AsmUtils.invokeConstructor(clazz);
        	Protos.mergeFrom(bytes, offset, destLength, entity);
        	return (T) entity;
        } else if (type == 1) {
        	MessageCollectionSchema<Object> collectionSchema = new MessageCollectionSchema<Object>(schema);
            List<Object> list = new CopyOnWriteArrayList<Object>();
            ProtobufIOUtil.mergeFrom(bytes, offset, destLength, list, collectionSchema);
            return (T) list;
        } else if (type == 2) {
        	MessageCollectionSchema<Object> collectionSchema = new MessageCollectionSchema<Object>(schema);
            Set<Object> set = new CopyOnWriteArraySet<Object>();
            ProtobufIOUtil.mergeFrom(bytes, offset, destLength, set, collectionSchema);
            return (T) set;
        } else if (type == 3) {
        	StringMapSchema<Object> collectionSchema = new StringMapSchema<Object>(schema);
            Map<String, Object> map = new ConcurrentHashMap<String, Object>();
            ProtobufIOUtil.mergeFrom(bytes, offset, destLength, map, collectionSchema);
            return (T) map;
        } else {
        	Object entity = AsmUtils.invokeConstructor(clazz);
        	ProtobufIOUtil.mergeFrom(bytes, entity, schema);
        	return (T) entity;
        }
    }

    private static int getLength(byte[] res) {
    	return (res[1] & 0xff) | ((res[2] << 8) & 0xff00) | ((res[3] << 24) >>> 8) | (res[4] << 24);
    }
}
